Android WearのWatch Faceの開発、作成した時のメモ書き@四角形・円形の形状判定など
時間が出来たので、久しぶりにAndoridのAPIの変更点やAndroid Studioの使い方を掘り下げて調べる為に、Watch faceを作成したのですが、その時の個人的なメモ書きです。
AndroidのプログラミングはJavaで行ってます。重ねて書きますが、これは個人的なメモ、備忘録ですので、ブログの文章は適当です。
本題の前に、今更ながら、EclipseのプロジェクトとAndroid Studioのモジュールについて少しメモ書き
本題の前に、今更ながらAndroid Studioの初歩的で必要な部分だけメモ書き!
EclipseからAndroid Studioへ移行するにあたり、最初に抑えておく事は、「Eclipseのプロジェクト = Android Studioのモジュール」で、「Android StudioのプロジェクトはEclipseのWork Spaceに当たる」という事でしょうか。
これが分かるとほぼAndroid Studioの半分が理解出来たといえると思いますw
Android StudioのEclipseの基本的な違い@初歩的なメモ
階層の移動は左上のドロップダウンメニューから行い、プログラムは基本「Android」で作成していく。
「Packages」では実際の階層が表示されるのだが、この「Android」では必要な要素のみ表示され、可読性が高くなる。
Eclipseのようにクラス、メソッドの構造を表示させたい場合、構造を表示させたいクラスを選択した後、左側のStructureタブをクリックする事で表示可能。クラス以外の構造も表示可能。
Log等の情報は下に表示される。
Android StudioでWatch Faceを作成する前にbuild.gradleのセットアップ
プロジェクトを作成してモジュールを作成したり、build.gradleについて等、基本的なところは割愛。ガイドに沿って作成すればある程度の雛形を作ってくれますね。
さて、Android StudioでWatch Faceを作成すると、build.gradleの項目を変更する必要がありました。
具体的には「dependencies」に自動で記述される「com.google.android.support:wearable:2.0.0-bata」のままだと下記がインポートできずにエラーになるので、1.4.0に手打ちで変更。
import android.support.wearable.watchface.CanvasWatchFaceService;
import android.support.wearable.watchface.WatchFaceService;
import android.support.wearable.watchface.WatchFaceStyle;
現時点では、この3つのWatch Faceに必要なライブラリーがインポートできずにエラーになります。
まぁベータとあるので、恐らくWatch Faceのライブラリだけまだ準備されてないようです。
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.support:wearable:1.4.0' // ここを変更
compile 'com.google.android.gms:play-services-wearable:10.0.1'
compile 'com.android.support:palette-v7:25.1.0'
}
で、上記のように1.4.0に変更すると、インポート出来るようになりました。情報がなかったのでメモ書きしてます。2.0が正式に公開されたらこの作業は必要なくなるでしょうね。
ここから本題、Android StudioでアナログのWatch Faceを作ってみた
ここから本題の、Android StudioでアナログのWatch Faceを作った時のメモ書きです。
まず、今更ながら、現時点でWatch Faceの公式の詳細の日本語ドキュメントはないし、今時は数年前書いた通り、Androidに関わらず、英語のドキュメントやフォーラム、ブログ等の情報からAPIやエラーについての情報を調べるしかない時代に本格的に入ったなと思いました。
うん、実際自分が調べたい事を日本語で検索してもなかったし、見つかっても2014年くらいの古い情報ばかりでほとんど役に立ちませんでしたね。
Android Wear、Watch Faceのはまった部分の解決方法メモ書き
さて、AndroidのWatch Face作成に当たり、少しはまった部分だけをメモ書きします。
AndroidのWatch FaceはActivityをextendsするのではなく、CanvasWatchFaceServiceをextendsして利用します。つまり、これはそのままでは、App(アプリ)のようにLayoutファイルが使えない事を意味しています。
なので、setContentView(~)メソッドが使えないし、stubでrect_layoutとround_layoutを分ける事がこのままでは出来ないのです。
もちろんめんどくさい事をすれば可能だけど、Watch Faceでそんな事をするのは、消費電力や保守の観点からナンセンスだと思います。
CanvasWatchFaceServiceを継承したクラスの、さらにCanvasWatchFaceService.Engineを継承したインナークラス(Engine)がメインの処理を行い、必要なメソッドがOrverideされているので、素直にそれらを使う事にしました。
※ つまり、ライブラリを使わずゲームアプリのように独自にFace Watchを作る事も可能だけど、ライブラリ使った方がメンテナンスが楽だし、Ambientモード等も利用出来る為、消費電力を抑えられるという事です。
この画像の(m)がメソッド。privateかpublicかは鍵マークで一目でわかりますね。描画はonDraw()で行うので、毎回書き換えが必要でない処理はonCreateで行い、描画は最小限にするように心がけました。
また、プログラムがどの順番に処理されていくのかをAndroid StudioのLogCatで見る為に、毎度おなじみの「Log」をはめ込んでいます。
どのようなライフサイクルを辿るのかについて、調べていたのです。
Rect(四角形)とRound(円形)でデザインを分ける方法
Rect(四角形)とRound(円形)でデザインを分ける方法が少しはまりどころだったので、メモ書きします。日本語ではWatch Faceに関しては新しい情報なかったね。
で、僕の場合、オーバーライドしたonApplyWindowInsetsメソッドの戻り値、booleanの戻り値を返すisRound()を利用し、Rect(四角形)かRound(円形)を判定し、デザインを合わせる事にしました。
因みに戻り値の一覧です。
/* 画面がRectかRoundかの判定メソッドだが、onCreate、onSurfaceViewの後に呼ばれるので、ここで通常のバックグラウンドとAmbientモード時の画像だけ再描画 */
@Override
public void onApplyWindowInsets(WindowInsets insets) {
super.onApplyWindowInsets(insets);
isRound = insets.isRound();
Log.i("onApplyWindowInsets","onApplyWindowInsetsが呼ばれました");
setmBackgroundBitmap();
initGrayBackgroundBitmap();
}
ここでさらっと書いているけど、このRectかRoundを判定するメソッドはonCreate、onSurfaceViewの後に呼ばれるので、onCreateでどちらかを判定してBackgroundのファイルを作成できないんですよね。
◆メソッドの呼び出し順番、流れ◆
これが今回の問題点である、RectかRoundか判定し、反映させるのに必要な主なメソッドの呼び出し順序。
- onCreate(作成)
- onSurfaceChanged(画面サイズなどの取得)
- initGrayBackgroundBitmap(Ambientモードの画像作成)
- onApplyWindowInsets(RectかRoundかの判定に利用)
- updateWatchHandStyle(Faceの状態が変わった際の処理を行う)
- onApplyWindowInsets(再度呼び出される)
で、これをどう解決するかについて、しばし悩んだ末の解決策が、onApplyWindowInsets()内、つまりRectかRoundかを判定するメソッド内で、setmBackgroundBitmap()とinitGrayBackgroundBitmap()を再度呼び出す事です。
まぁこれが一番シンプルで簡単な方法でした。
これで分かったかもしれないけれど、通常時の画像と、Ambientモード時の画像は別に用意できますし、用意しない場合でも、Ambientモードの画像をRectかRoundかを判定したのち、initGrayBackgroundBitmap()に再度設定しないと反映されません。
因みにこのsetmBackgroundBitmap()は僕が作ったメソッドです。雛形だと全てonCreate内で作成されるので、意図的に分けて呼び出してます。
これも意図的に分けて作成したcreateMiku()メソッド内で呼び出すのですが、このRectかRoundかの判定後に、最低限行う必要がある処理の部分だけ、さらに意図的に分けて作成したメソッドです。
※ 必要最低限の処理を行う = 消費電力の節約という感じです。
いずれにしてもonCreate内で一度呼び出さないと、この時点でエラーになるのですが、onCreate()は言わずものがなAndroidでは以前と変わらず最初に呼ばれるメソッドなので、onApplyWindowInsets()で再度作成するという感じにしました。
因みにonApplyWindowInsets()をonCreate()内で呼び出せるか試したけど、エラーで呼び出せませんでした。2.0正式版で解決するといいですね!
あと一つ、消費電力の節約とプライバシーの配慮につながる処理を追加していますので、どうぞ下記を参考にしてください。
@Override
public void onCreate(SurfaceHolder holder) {
super.onCreate(holder);
setWatchFaceStyle(new WatchFaceStyle.Builder(VocaloWatchFaces.this)
.setCardPeekMode(WatchFaceStyle.PEEK_MODE_SHORT)
.setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_HIDDEN) // Ambientモード時に通知カードを非表示で、プライバシーの配慮と電力節約!
.setBackgroundVisibility(WatchFaceStyle.BACKGROUND_VISIBILITY_INTERRUPTIVE)
.setShowSystemUiTime(false)
.setAcceptsTapEvents(true)
.build());
mConfig = new Config(VocaloWatchFaces.this, null);
mConfig.connect();
creatMiku();
/* Extract colors from background image to improve watchface style. */
Palette.from(mBackgroundBitmap).generate(new Palette.PaletteAsyncListener() {
@Override
public void onGenerated(Palette palette) {
if (palette != null) {
mWatchHandHighlightColor = palette.getVibrantColor(Color.RED);
mWatchHandColor = palette.getLightVibrantColor(Color.WHITE);
mWatchHandShadowColor = palette.getDarkMutedColor(Color.BLACK);
updateWatchHandStyle();
}
}
});
mCalendar = Calendar.getInstance();
}
onCreate()の中で、setWatchFaceStyleを呼び出してるのだけど、ここで.setAmbientPeekMode(WatchFaceStyle.AMBIENT_PEEK_MODE_HIDDEN)を追加しています。スマホでもそうだけど、画面が暗い時にガンガン通知来る = 通信して電力消費するよりは、画面が明るくなったタイミングでまとめて通知を受けた方がいいと思えるので、そうしてます。
もちろんこれは好き好きなので、参考程度にどうぞ的な感じです。
適当なまとめ
ついでに画像はdrawableフォルダではなく、画質重視ならばAndroid4.2以降で使えるようになったmipmapフォルダに入れた方がいいと思います。なぜかって?それは調べるか試してみましょう~。
因みに作成したWatch Faceはこんな感じでmoto360のようなChin型でもSquare型でも正常に表示&機能しました。あ、コンパスアプリに使用した画像流用してますw
因みにGoogle Playに公開しています。
→ 初音ミク(Hatsune Miku) Analog Watch Face
→ Amazonは後日うぷる。
まぁ学習がてら作った消費電力に注意して作成した実用重視の適当なアプリですが、後日キャラクター変更出来たり、まともなデザインのバージョンをうぷると思います。
どうでもいいけど僕は兼好法師の言葉の、
「上手くできないうちは、できるだけ人に知られないようにして、こっそり練習して上手くできるようになってから、人前に出る人は、一芸といえども習得することはできない~云々」を教訓としていますw
どうでもいい蛇足でしたw
まぁ本当に細かい事は書いてないただのメモ書きで、要するに、RectとRoundの判定のタイミングが遅いので、工夫する必要がある事を備忘録的に残したかっただけですけど、参考になれば幸いです。でゎでゎ。
追記@Android Studioでapk書き出しエラーを解決した時のメモ書き
署名して書き出す時にエラーが出たので、その解決策のメモ書きです。
まず、普通に書き出すと下記のエラーが出ました。
Error:Execution failed for task ':app:transformClassesWithDexForRelease'.
com.android.build.api.transform.TransformException:
com.android.ide.common.process.ProcessException:
java.util.concurrent.ExecutionException: com.android.dex.DexException:
Multiple dex files define Landroid/support/v4/accessibilityservice/AccessibilityServiceInfoCompat$AccessibilityServiceInfoVersionImpl;
まぁsupport-v4ライブラリーのエラーなのだけど、下記ドキュメントにヒントがあった(日本語)けど、ハンドヘルド端末との通信有無に関わらず必要なライブラリなので追加。
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.google.android.support:wearable:1.4.0'
compile 'com.google.android.gms:play-services-wearable:10.0.1'
compile 'com.android.support:palette-v7:25.1.0'
compile 'com.android.support:support-v4:25.1.0' // 追加
}
これでビルドすると、次に下記のエラーが出た
Error:Execution failed for task ':app:transformClassesWithDexForRelease'.
com.android.build.api.transform.TransformException:
com.android.ide.common.process.ProcessException:
java.util.concurrent.ExecutionException:
com.android.dex.DexIndexOverflowException:
method ID not in [0, 0xffff]: 65536
調べてみたら64K制限というやつに引っかかったので、下記のドキュメントを参考にtrueにして解決
buildTypes {
release {
minifyEnabled true // ここをtrueに変更
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
あとは、僕が使ってるのはWearableアプリ用の「com.google.android.gms:play-services-wearable」なので、AndroidManifest.xml内のメタタグ、「com.google.android.gms」を削除。
これで無事に書き出しが出来ました。
以上!